package 正则表达式;

//我们前面讲到的(...)可以用来把一个子规则括起来，这样写learn\s(java|php|go)就可以更方便地匹配长字符串了。
//
//实际上(...)还有一个重要作用，就是分组匹配。
//我们来看一下如何用正则匹配区号-电话号码这个规则。利用前面讲到的匹配规则，写出来很容易：
//\d{3,4}\-\d{6,8}
//虽然这个正则匹配规则很简单，但是往往匹配成功后，下一步是提取区号和电话号码，分别存入数据库。于是问题来了：如何提取匹配的子串？
//当然可以用String提供的indexOf()和substring()这些方法，但它们从正则匹配的字符串中提取子串没有通用性，下一次要提取learn\s(java|php)还得改代码。
//正确的方法是用(...)先把要提取的规则分组，把上述正则表达式变为(\d{3,4})\-(\d{6,8})。
//现在问题又来了：匹配后，如何按括号提取子串？
//
//现在我们没办法用String.matches()这样简单的判断方法了，必须引入java.util.regex包，用Pattern对象匹配，匹配后获得一个Matcher对象，如果匹配成功，
//就可以直接从Matcher.group(index)返回子串：

/*import java.util.regex.*;
public class 分组匹配 {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        Matcher m = p.matcher("010-12345678");
        if (m.matches()) {
            String g1 = m.group(1);
            String g2 = m.group(2);
            System.out.println(g1);
            System.out.println(g2);
        } else {
            System.out.println("匹配失败!");
        }
    }
}*/

//运行上述代码，会得到两个匹配上的子串010和12345678。
//
//要特别注意，Matcher.group(index)方法的参数用1表示第一个子串，2表示第二个子串。如果我们传入0会得到什么呢？答案是010-12345678，即整个正则匹配到的字符串。
//
//Pattern
//我们在前面的代码中用到的正则表达式代码是String.matches()方法，而我们在分组提取的代码中用的是java.util.regex包里面的Pattern类和Matcher类。实际上
//这两种代码本质上是一样的，因为String.matches()方法内部调用的就是Pattern和Matcher类的方法。

//String.matches(String regex)运行调用轨迹
//
//String.matches(String regex):
//public boolean matches(String regex) {
//    return Pattern.matches(regex, this);
//}
//
//Pattern.matches(String regex, CharSequence input):
//public static boolean matches(String regex, CharSequence input) {
//    Pattern p = Pattern.compile(regex);
//    Matcher m = p.matcher(input);
//    return m.matches();
//}
//
//Matches.matches():
//static final int ENDANCHOR = 1;
//int from;
//int oldLast = -1;
//public boolean matches() {
//    return match(from, ENDANCHOR);
//}
//
//Matches.match(int from, int anchor)
//boolean match(int from, int anchor) {
//    this.hitEnd = false;
//    this.requireEnd = false;
//    from        = from < 0 ? 0 : from;
//    this.first  = from;
//    this.oldLast = oldLast < 0 ? from : oldLast;
//    for (int i = 0; i < groups.length; i++)
//        groups[i] = -1;
//    for (int i = 0; i < localsPos.length; i++) {
//        if (localsPos[i] != null)
//            localsPos[i].clear();
//    }
//    acceptMode = anchor;
//    boolean result = parentPattern.matchRoot.match(this, from, text);
//    if (!result)
//        this.first = -1;
//    this.oldLast = this.last;
//    this.modCount++;
//    return result;
//}


//但是反复使用String.matches()对同一个正则表达式进行多次匹配效率较低，因为每次都会创建出一样的Pattern对象。完全可以先创建出一个Pattern对象，然后反复使
//用，就可以实现编译一次，多次匹配：
//
/*import java.util.regex.*;
public class 分组匹配 {
    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");

        System.out.println("010-12345678".matches("(\\d{3,4})\\-(\\d{7,8})"));//true
        System.out.println("021-123456".matches("(\\d{3,4})\\-(\\d{7,8})"));//false
        System.out.println("022#1234567".matches("(\\d{3,4})\\-(\\d{7,8})"));//false

        pattern.matcher("010-12345678").matches(); // true
        pattern.matcher("021-123456").matches(); // true???
        System.out.println(pattern.matcher("021-123456").matches());//false
        
        pattern.matcher("022#1234567").matches(); // false
        // 获得Matcher对象:
        Matcher matcher = pattern.matcher("010-12345678");
        if (matcher.matches()) {
            String whole = matcher.group(0); // "010-12345678", 0表示匹配的整个字符串
            String area = matcher.group(1); // "010", 1表示匹配的第1个子串
            String tel = matcher.group(2); // "12345678", 2表示匹配的第2个子串
            System.out.println(area);
            System.out.println(tel);
        }
    }
}*/
//
//使用Matcher时，必须首先调用matches()判断是否匹配成功，匹配成功后，才能调用group()提取子串。
//
//利用提取子串的功能，我们轻松获得了区号和号码两部分。
//
//练习
//利用分组匹配，从字符串"23:01:59"提取时、分、秒。
//
//从下载练习：分组匹配 （推荐使用IDE练习插件快速下载）
//
//小结
//正则表达式用(...)分组可以通过Matcher对象快速提取子串：
//
//group(0)表示匹配的整个字符串；
//group(1)表示第1个子串，group(2)表示第2个子串，以此类推
/*
一、Pattern类
Pattern类用于创建一个正则表达式，也可以说创建一个匹配模式，它的构造方法是私有的，不可以直接创建，但可以通过 Pattern.complie(String regex) 简单
工厂方法创建一个正则表达式
Pattern p = Pattern.compile("\\w+"); 
p.pattern(); //返回 \w+ 
pattern() 返回正则表达式的字符串形式，其实就是返回Pattern.complile(String regex)的regex参数。

1、Pattern.split(CharSequence input)
Pattern有一个 split(CharSequence input) 方法用于分隔字符串，并返回一个String[]。我猜 String.split(String regex) 就是通过Pattern.split(CharSequence input)来实现的
Pattern p=Pattern.compile("\\d+"); 
String[] str=p.split("我的QQ是:456456我的电话是:0532214我的邮箱是:aaa@aaa.com"); 
//结果:str[0]="我的QQ是:" str[1]="我的电话是:" str[2]="我的邮箱是:aaa@aaa.com"

2、Pattern.matcher(String regex,CharSequence input)是一个静态方法，用于快速匹配字符串，该方法适合用于只匹配一次，且匹配全部字符串
Pattern.matches("\\d+","2223");//返回true 
Pattern.matches("\\d+","2223aa");//返回false,需要匹配到所有字符串才能返回true,这里aa不能匹配到

3、Pattern.matcher(CharSequence input)
Pattern.matcher(CharSequence input)返回一个Matcher对象。
Matcher类的构造方法也是私有的，不能随意创建，只能通过 Pattern.matcher(CharSequence input) 方法得到该类的实例。
Pattern类只能做一些简单的匹配操作，要想得到更强更便捷的正则匹配操作，那就需要将 Pattern与Matcher 一起合作。Matcher类提供了对正则表达式的分组支持，
以及对正则表达式的多次匹配支持。
Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("22bb23"); 
m.pattern();//返回p 也就是返回该Matcher对象是由哪个Pattern对象的创建的

二、Matcher类

1、Matcher.matches()、 Matcher.lookingAt()、Matcher.find()
Matcher类提供三个匹配操作方法，三个方法均返回boolean类型，当匹配到时返回true，没匹配到则返回false 。

（1）matches()对整个字符串进行匹配，只有整个字符串都匹配了才返回true。
Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("22bb23"); 
m.matches();//返回false,因为bb不能被\d+匹配,导致整个字符串匹配未成功. 
Matcher m2=p.matcher("2223"); 
m2.matches();//返回true,因为\d+匹配到了整个字符串
我们现在回头看一下Pattern.matcher(String regex,CharSequence input)，它与下面这段代码等
价 Pattern.compile(regex).matcher(input).matches()

（2）lookingAt() 对前面的字符串进行匹配，只有匹配到的字符串在最前面才返回true
Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("22bb23"); 
m.lookingAt();//返回true,因为\d+匹配到了前面的22 
Matcher m2=p.matcher("aa2223"); 
m2.lookingAt();//返回false,因为\d+不能匹配前面的aa 

（3）find()对字符串进行匹配，匹配到的字符串可以在任何位置
Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("22bb23"); 
m.find();//返回true 
Matcher m2=p.matcher("aa2223"); 
m2.find();//返回true 
Matcher m3=p.matcher("aa2223bb"); 
m3.find();//返回true 
Matcher m4=p.matcher("aabb"); 
m4.find();//返回false 

2、Mathcer.start()、 Matcher.end()、 Matcher.group()
当使用matches()、lookingAt()、find()执行匹配操作后，就可以利用以上三个方法得到更详细的信息。

（1）start()返回匹配到的子字符串在字符串中的索引位置

（2）end()返回匹配到的子字符串的最后一个字符在字符串中的索引位置

（3）group()返回匹配到的子字符串

Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("aaa2223bb"); 
m.find();//匹配2223 
m.start();//返回3 
m.end();//返回7,返回的是2223后的索引号 
m.group();//返回2223 

Mathcer m2=m.matcher("2223bb"); 
m.lookingAt();   //匹配2223 
m.start();   //返回0,由于lookingAt()只能匹配前面的字符串,所以当使用lookingAt()匹配时,start()方法总是返回0 
m.end();   //返回4 
m.group();   //返回2223 

Matcher m3=m.matcher("2223bb"); 
m.matches();   //匹配整个字符串 
m.start();   //返回0,原因相信大家也清楚了 
m.end();   //返回6,原因相信大家也清楚了,因为matches()需要匹配所有字符串 
m.group();   //返回2223bb 

三、分组在Java中如何使用
相信大家都明白了以上几个方法的使用，现在我们该说说正则表达式的分组在java中是怎么使用的。

start()、end()、group()均有一个重载方法，它们是start(int i)、end(int i)、group(int i)专用于分组操作，Mathcer类还有一个groupCount()用于
返回有多少组。

Pattern p=Pattern.compile("([a-z]+)(\\d+)"); 
Matcher m=p.matcher("aaa2223bb"); 
m.find();   //匹配aaa2223 
m.groupCount();   //返回2,因为有2组 
m.start(1);   //返回0 返回第一组匹配到的子字符串在字符串中的索引号 
m.start(2);   //返回3 
m.end(1);   //返回3 返回第一组匹配到的子字符串的最后一个字符在字符串中的索引位置. 
m.end(2);   //返回7 
m.group(1);   //返回aaa,返回第一组匹配到的子字符串 
m.group(2);   //返回2223,返回第二组匹配到的子字符串 

现在我们使用一下稍微高级点的正则匹配操作，例如有一段文本里面有很多数字，而且这些数字是分开的，我们现在要将文本中所有数字都取出来，利用java的正则操作是那么的简单。

Pattern p=Pattern.compile("\\d+"); 
Matcher m=p.matcher("我的QQ是:456456 我的电话是:0532214 我的邮箱是:aaa123@aaa.com"); 
while(m.find()) { 
   System.out.println(m.group()); 
} 

//输出: 
//456456 
//0532214 
//123 

//如将以上while()循环替换成 
while(m.find()) { 
   System.out.println(m.group()); 
   System.out.print("start:"+m.start()); 
   System.out.println(" end:"+m.end()); 
} 

//则输出: 
456456 
start:6 end:12 
0532214 
start:19 end:26 
123 
start:36 end:39 

现在大家应该知道：每次执行匹配操作后start()、end()、group()三个方法的值都会改变成匹配到的子字符串的信息，以及它们的重载方法也会改变成相应的信息。

注意：只有当匹配操作成功才可以使用start()、end()、group()三个方法，否则会
抛出java.lang.IllegalStateException，也就是当matches()、lookingAt()、find()其中任意一个方法返回true时才可以使用。
*/
